package com.hy.${packageCode}.shared.utils;
import com.hy.${packageCode}.shared.defs.SharedDefs;
import com.hy.${packageCode}.shared.defs.SharedDefs;
import com.hy.${packageCode}.shared.defs.SharedDefs;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.ResourcePropertySource;
import org.springframework.util.ClassUtils;

import java.util.Map;
import java.util.Optional;

@Slf4j
//@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class SpringUtil implements SpringApplicationRunListener {
    private static final String PRIORITY_CONTEXT_CLASS = "org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

    private static final String BOOTSTRAP_COMMON_PROPERTIES_RESOURCE = "/bootstrap-embedded-common.properties";
    private static final String BOOTSTRAP_LOCAL_PROPERTIES_RESOURCE = "/bootstrap-embedded-local.properties";

    public SpringUtil(SpringApplication springApplication, String[] args) {
    }


    private static ConfigurableApplicationContext applicationContext;

    @SneakyThrows
    private static boolean isPriorityContext(ConfigurableApplicationContext context) {
        if (!ClassUtils.isPresent(PRIORITY_CONTEXT_CLASS, context.getClassLoader())) {
            return false;
        }
        Class<?> priorityContextClass = ClassUtils.forName(PRIORITY_CONTEXT_CLASS, context.getClassLoader());
        return ClassUtils.isAssignableValue(priorityContextClass, context);
    }

    public static void setApplicationContext(ConfigurableApplicationContext context) {
        log.info("Setting the application context {}", context);
        if (applicationContext == null || !applicationContext.isActive()) {
            applicationContext = context;
        } else if (context.isActive() || isPriorityContext(context)) {
            applicationContext = context;
        } else {
            log.warn("Will not setting the application context {}", context);
        }
    }

    public static <T> T getBean(Class<T> type) {
        return applicationContext.getBean(type);
    }

    public static <T> Optional<T> getBeanOrEmpty(Class<T> type) {
        try {
            return Optional.of(getBean(type));
        } catch (Exception e) {
            return Optional.empty();
        }
    }

    public static <T> T getBean(String name) {
        return (T) applicationContext.getBean(name);
    }

    public static <T> Optional<T> getBeanOrEmpty(String name) {
        try {
            return Optional.of(getBean(name));
        } catch (Exception e) {
            return Optional.empty();
        }
    }

    public static <T> Map<String, T> getBeansOfType(Class<T> type) {
        return applicationContext.getBeansOfType(type);
    }

    public static <T> T autowire(T javaBean) {
        applicationContext.getAutowireCapableBeanFactory().autowireBean(javaBean);
        return javaBean;
    }

    @Override
    @SneakyThrows
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
                                    ConfigurableEnvironment environment) {
        MutablePropertySources mutablePropertySources = environment.getPropertySources();

        if (environment.acceptsProfiles(SharedDefs.LOCAL_PROFILE) && !mutablePropertySources.contains(BOOTSTRAP_LOCAL_PROPERTIES_RESOURCE)) {
            mutablePropertySources.addLast(readEmbeddedProperties(BOOTSTRAP_LOCAL_PROPERTIES_RESOURCE));
        }

        if (!mutablePropertySources.contains(BOOTSTRAP_COMMON_PROPERTIES_RESOURCE)) {
            mutablePropertySources.addLast(readEmbeddedProperties(BOOTSTRAP_COMMON_PROPERTIES_RESOURCE));
        }
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        setApplicationContext(context);
    }




    protected ResourcePropertySource readEmbeddedProperties(String source) {
        return readEmbeddedProperties(source, SpringUtil.class);
    }

    @SneakyThrows
    protected ResourcePropertySource readEmbeddedProperties(String source, Class<?> readFrom) {
        return new ResourcePropertySource(source, new ClassPathResource(source, readFrom));
    }
}
